﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Reflection;
using System.Globalization;
using System.Web.Routing;
using WebDotNet.RouteHandlers;

namespace WebDotNet.HttpHandlers
{
	public class RequestHandler : IHttpHandler
	{
		private static readonly List<KeyValuePair<string, Type>> _urlHandlers = new List<KeyValuePair<string, Type>>();

		public static List<KeyValuePair<string, Type>> UrlHandlers
		{
			get
			{
				return _urlHandlers;
			}
		}

		public static void RegisterUrlHandler(string url, Type handler)
		{
			if (url == null) throw new ArgumentNullException("url");
			if (handler == null) throw new ArgumentNullException("handler");

			UnregisterHandler(url);

			_urlHandlers.Add(new KeyValuePair<string, Type>(url, handler));
			RouteTable.Routes.Add(new Route(url, new DefaultRouteHandler()));
		}

		public static void UnregisterHandler(string url)
		{
			if (url == null) throw new ArgumentNullException("url");

			for (int counter = _urlHandlers.Count - 1; counter > -1; counter--)
			{
				var existingHandler = _urlHandlers[counter];

				if (existingHandler.Key == url)
				{
					// todo:  unregister
					_urlHandlers.Remove(existingHandler);
				}
			}
		}

		public bool IsReusable
		{
			get
			{
				return false;
			}
		}

		public void ProcessRequest(HttpContext httpContext)
		{
			ProcessRequest(new HttpContextWrapper(httpContext));
		}

		public void ProcessRequest(HttpContextBase httpContextBase)
		{
			if (httpContextBase == null) throw new ArgumentNullException("httpContextBase");
			if (httpContextBase.ApplicationInstance == null) throw new ArgumentNullException("httpContextBase.ApplicationInstance");

			Type type = _urlHandlers.FirstOrDefault(re => re.Key == ((Route)httpContextBase.Request.RequestContext.RouteData.Route).Url).Value; // I dont know why RouteBase doesnt have a property named Url.

			object instance = createInstance(type);

			List<object> parameters = new List<object>() { httpContextBase };
			parameters.AddRange(httpContextBase.Request.RequestContext.RouteData.Values.Select(kvp => kvp.Value));

			MethodInfo methodInfo = getMethod(type, httpContextBase.Request.HttpMethod, parameters.ToArray());

			try
			{
				StringBuilder buffer = executeMethod<StringBuilder>(instance, methodInfo, parameters.ToArray());

				if (buffer != null)
				{
					httpContextBase.Response.Write(buffer.ToString());
				}
			}
			catch (Exception exception)
			{
				// do something here
				throw;
			}
		}

		private object createInstance(Type type)
		{
			object instance = Activator.CreateInstance(type);
			return instance;
		}

		private MethodInfo getMethod(Type type, string methodName, object[] parameters)
		{
			if (type == null) throw new ArgumentNullException("type");
			if (string.IsNullOrEmpty(methodName)) throw new ArgumentNullException("methodName");

			var parameterTypeArray = (parameters ?? new object[0]).Select(p => p.GetType()).ToArray();
			MethodInfo methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance, Type.DefaultBinder, parameterTypeArray, null);

			if (methodInfo == null)
			{
				// raise not implemented event
				throw new NotImplementedException();
			}

			return methodInfo;
		}

		private T executeMethod<T>(object instance, MethodInfo methodInfo, object[] parameters)
		{
			if (instance == null) throw new ArgumentNullException("instance");
			if (methodInfo == null) throw new ArgumentNullException("methodInfo");

			object returnValue = methodInfo.Invoke(instance, BindingFlags.Default, Type.DefaultBinder, parameters, CultureInfo.CurrentCulture);

			if (methodInfo.ReturnType == typeof(void))
			{
				return default(T);
			}
			else
			{
				return (T)returnValue;
			}
		}
	}
}
